﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using SharpGL;
using System.Runtime.CompilerServices;
using System.Threading;
using System.ComponentModel;

namespace ConnectFour
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        const int CIRCLE_RESOLUTION = 32, //must be a power of two, 4 minimum
            ROWS = 6,
            COLUMNS = 7,
            HALF_COLUMNS = COLUMNS / 2,
            MAX_BOUNCES = 4;
        const double MODULE_SIZE = 10.0,
            HALF_MODULE_SIZE = MODULE_SIZE / 2.0,
            FRAME_THICKNESS = 0.3,
            CHECKER_THICKNESS = 1.0,
            FRAME_BAR_OFFSET_X = HALF_COLUMNS * MODULE_SIZE + HALF_MODULE_SIZE,
            CHECKER_PILE_OFFSET = MODULE_SIZE;



        double[] circleX, circleY;
        float[][] frameNormals, checkerOuterNormals, checkerInnerNormals;
        byte[] wood = new byte[1024*1024*3];
        double angle = 0.0;
        bool animation = false, boardFull = false, animation2 = false, boardClearing = false, moderatorGood = false;
        double movingCheckerX, movingCheckerY, movingVelY, gravity = -.5, movingTargetY, movingBoardY = 0;
        int movingRow = -1, movingCol = -1, movingBounceCount = 0;

        Plastic currentPlastic = Plastic.Black; //initial value other than yellow so the first set to yellow works
        Random random = new Random();
        List<String> players = new List<String> { "miles", "tyler", "jake", "tony", "reid", "ryan", "aaron", "random" , "human"};
        Moderator moderator;
        BackgroundWorker gameWorker, boardClearer;
        Checker movingChecker, winner;
        Board tempBoard = null;

        public MainWindow()
        {
            circleX = new double[CIRCLE_RESOLUTION];
            circleY = new double[CIRCLE_RESOLUTION];
            frameNormals = new float[CIRCLE_RESOLUTION][];
            checkerOuterNormals = new float[CIRCLE_RESOLUTION][];
            checkerInnerNormals = new float[CIRCLE_RESOLUTION][];

            for (int i = 0; i < CIRCLE_RESOLUTION; i++)
            {
                circleX[i] = Math.Cos((Math.PI * i) / (CIRCLE_RESOLUTION / 2));
                circleY[i] = Math.Sin((Math.PI * i) / (CIRCLE_RESOLUTION / 2));
            }
            /*board = new Checker[ROWS, COLUMNS];
            for (int i = 0; i < ROWS; i++)
            {
                for (int j = 0; j < COLUMNS; j++)
                {
                    board[i, j] = Checker.Empty;
                }
            }*/

            InitializeComponent();

            for (int i = 0; i < players.Count; i++)
            {
                comboBoxBlack.Items.Add(players[i]);
                comboBoxRed.Items.Add(players[i]);
            }

            gameWorker = new BackgroundWorker();
            gameWorker.DoWork += new DoWorkEventHandler(gameWorker_DoWork);
            gameWorker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(gameWorker_RunWorkerCompleted);
            boardClearer = new BackgroundWorker();
            boardClearer.DoWork += new DoWorkEventHandler(boardClearer_DoWork);


            //texturing nonsense
            System.Drawing.Bitmap img = new System.Drawing.Bitmap("wood.jpg");
            System.Drawing.Imaging.BitmapData raw = null;  
            byte[] rawImage = null; 

            try
            {
                //Freeze the image in memory
                raw = img.LockBits(
                    new System.Drawing.Rectangle(0, 0, (int)img.Width, (int)img.Height),
                    System.Drawing.Imaging.ImageLockMode.ReadOnly,
                    System.Drawing.Imaging.PixelFormat.Format24bppRgb
                );

                int size = raw.Height * raw.Stride;
                rawImage = new byte[size];

                //Copy the image into the byte[]
                System.Runtime.InteropServices.Marshal.Copy(raw.Scan0, rawImage, 0, size);
            }
            finally
            {
                if (raw != null)
                {
                    //Unfreeze the memory for the image
                    img.UnlockBits(raw);
                }
            }

            for (int i = 0; i < 1024; i++)
            {
                for (int j = 0; j < 1024*3; j++)
                {
                    wood[i * 1024*3 + j] = rawImage[i * 3008*3 + j];
                }
            }
        }
        private void openGLControl_OpenGLInitialized(object sender, SharpGL.OpenGLEventArgs args)
        {
            OpenGL gl = args.OpenGL;
            gl.Enable(OpenGL.GL_DEPTH_TEST);
            
            gl.Enable(OpenGL.GL_LIGHTING);
            gl.Enable(OpenGL.GL_LIGHT0);
            gl.Enable(OpenGL.GL_NORMALIZE);
            gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_AMBIENT, new float[] { 0.3f, 0.3f, 0.3f, 1.0f });
            gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_DIFFUSE, new float[] { 1.0f, 1.0f, 1.0f, 1.0f });
            gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_SPECULAR, new float[] { 1.0f, 1.0f, 1.0f, 1.0f });
            gl.ClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            setPlasticColor(gl, Plastic.Yellow);

            gl.Enable(OpenGL.GL_TEXTURE_2D);
            gl.TexImage2D(OpenGL.GL_TEXTURE_2D, 0, 3, 1024, 1024, 0, OpenGL.GL_BGR, OpenGL.GL_UNSIGNED_BYTE, wood);
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_WRAP_S, OpenGL.GL_REPEAT);
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_WRAP_T, OpenGL.GL_REPEAT);
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MAG_FILTER, OpenGL.GL_LINEAR);
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MIN_FILTER, OpenGL.GL_LINEAR);
            //gl.TexEnv(OpenGL.GL_TEXTURE_ENV, OpenGL.GL_TEXTURE_ENV_MODE, OpenGL.GL_REPLACE);
        }

        private void openGLControl_Resized(object sender, SharpGL.OpenGLEventArgs args)
        {
            OpenGL gl = args.OpenGL;
            gl.MatrixMode(OpenGL.GL_PROJECTION);
            gl.LoadIdentity();
            gl.Perspective(45.0f, (float)gl.RenderContextProvider.Width / (float)gl.RenderContextProvider.Height, 0.1f, 300.0f);
            gl.MatrixMode(OpenGL.GL_MODELVIEW);
        }

        private void openGLControl_OpenGLDraw(object sender, SharpGL.OpenGLEventArgs args)
        {
            //hack to fix memory leak in SharpGL
            GC.Collect();

            OpenGL gl = args.OpenGL;

            gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
            gl.PolygonMode(OpenGL.GL_FRONT, OpenGL.GL_FILL);
            gl.PolygonMode(OpenGL.GL_BACK, OpenGL.GL_LINE);

            //gl.Color(1f, 0, 0);

            gl.LoadIdentity();

            gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, new float[] { 0.0f, 5.0f, 20.0f });

            gl.LookAt(/*Math.Min(Math.Max(75 * Math.Cos(.2*angle), -60), 60)*/ 35*Math.Cos(angle), 
                20 /*+ Math.Cos(2 * angle) * 35*/, 
                /*Math.Max(Math.Abs(25 * Math.Sin(.2*angle)) + 75f, 90)*/90, 0, 30, 0, 0, 1, 0);

            //gl.LookAt(35, 0, 0, 35, 80, 0, -1, 0, 0);
            angle += 0.01;
            

            #region Draw frame


            setPlasticColor(gl, Plastic.Yellow);

            for (int i = -HALF_COLUMNS; i <= HALF_COLUMNS; i++)
            {
                for (int j = 1; j <= ROWS; j++)
                {
                    //offset i and j to keep the board centered 
                    drawFrameModule(gl, i * MODULE_SIZE, j * MODULE_SIZE);
                    gl.Rotate(0, 180, 0);
                    drawFrameModule(gl, i * MODULE_SIZE, j * MODULE_SIZE);
                    gl.Rotate(0, 180, 0);
                }
            }

            gl.Begin(OpenGL.GL_QUADS);

            //left bar
            gl.Normal(1.0, 0.0, 0.0);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 4.5, -1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65.5, -1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65.5, 1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 4.5, 1.5);

            gl.Normal(-1.0, 0.0, 0.0);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 4.5, 1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 65.5, 1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 65.5, -1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 4.5, -1.5);

            gl.Normal(0.0, 0.0, 1.0);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 4.5, 1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65.5, 1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 65.5, 1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 4.5, 1.5);

            gl.Normal(0.0, 0.0, -1.0);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 4.5, -1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 65.5, -1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65.5, -1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 4.5, -1.5);

            gl.Normal(0.0, 1.0, 0.0);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65.5, -1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 65.5, -1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 65.5, 1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65.5, 1.5);

            gl.Normal(0.0, -1.0, 0.0);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 4.5, 1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 4.5, 1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X - FRAME_THICKNESS, 4.5, -1.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 4.5, -1.5);


            //right bar
            gl.Normal(-1.0, 0.0, 0.0);
            gl.Vertex(FRAME_BAR_OFFSET_X, 4.5, 1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 65.5, 1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 65.5, -1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 4.5, -1.5);

            gl.Normal(1.0, 0.0, 0.0);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 4.5, -1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 65.5, -1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 65.5, 1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 4.5, 1.5);

            gl.Normal(0.0, 0.0, 1.0);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 4.5, 1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 65.5, 1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 65.5, 1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 4.5, 1.5);

            gl.Normal(0.0, 0.0, -1.0);
            gl.Vertex(FRAME_BAR_OFFSET_X, 4.5, -1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 65.5, -1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 65.5, -1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 4.5, -1.5);

            gl.Normal(0.0, 1.0, 0.0);
            gl.Vertex(FRAME_BAR_OFFSET_X, 65.5, 1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 65.5, 1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 65.5, -1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 65.5, -1.5);

            gl.Normal(0.0, -1.0, 0.0);
            gl.Vertex(FRAME_BAR_OFFSET_X, 4.5, -1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 4.5, -1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X + FRAME_THICKNESS, 4.5, 1.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 4.5, 1.5);

            //top/bottom covers

            //gl.Normal(0.0, 1.0, 0.0);
            gl.Vertex(FRAME_BAR_OFFSET_X, 65, 0.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65, 0.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65, 0.5 + FRAME_THICKNESS);
            gl.Vertex(FRAME_BAR_OFFSET_X, 65, 0.5 + FRAME_THICKNESS);

            gl.Vertex(FRAME_BAR_OFFSET_X, 65, -0.5 - FRAME_THICKNESS);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65, -0.5 - FRAME_THICKNESS);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 65, -0.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 65, -0.5);

            //gl.Normal(0.0, -1.0, 0.0);
            gl.Vertex(FRAME_BAR_OFFSET_X, 5, 0.5 + FRAME_THICKNESS);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 5, 0.5 + FRAME_THICKNESS);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 5, 0.5);
            gl.Vertex(FRAME_BAR_OFFSET_X, 5, 0.5);

            gl.Vertex(FRAME_BAR_OFFSET_X, 5, -0.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 5, -0.5);
            gl.Vertex(-FRAME_BAR_OFFSET_X, 5, -0.5 - FRAME_THICKNESS);
            gl.Vertex(FRAME_BAR_OFFSET_X, 5, -0.5 - FRAME_THICKNESS);

            gl.End();

            #endregion

            #region draw checkers
            //draw checkers in the board
            if (moderator != null)
            {
                gl.PushMatrix();
                gl.Translate(-30, 10, 0);
                gl.Rotate(-90, 0, 0);
                for (int i = 0; i < ROWS; i++)
                {
                    for (int j = 0; j < COLUMNS; j++)
                    {
                        if(i != movingRow || j != movingCol)
                            drawChecker(gl, boardClearing ? tempBoard[i,j] : moderator.Board[i, j], j * 10, 0, i * 10 + movingBoardY, 0);
                    }
                }

                if (animation)
                {
                    drawChecker(gl, movingChecker, movingCheckerX, 0 ,movingCheckerY, 0);
                    movingVelY += gravity;
                    movingCheckerY += movingVelY;
                    if (movingCheckerY < movingTargetY)
                    {
                        movingVelY = -(movingVelY*.35);
                        movingCheckerY += 2 * (movingTargetY - movingCheckerY);
                        movingBounceCount++;
                    }
                    if (movingBounceCount == MAX_BOUNCES)
                    {
                        animation = false;
                        movingRow = -1;
                        movingCol = -1;
                    }
                }

                if (animation2)
                {
                    movingVelY += gravity;
                    movingBoardY += movingVelY;

                    if (movingBoardY < movingTargetY)
                    {
                        animation2 = false;
                        movingBoardY = 0;
                        boardFull = false;
                    }
                }
                gl.PopMatrix();
                //draw checkers in piles

                for (int i = 0; i < moderator.Board.Remaining[(int)Checker.Red]; i++)
                {
                    drawChecker(gl, Checker.Red, FRAME_BAR_OFFSET_X + CHECKER_PILE_OFFSET, i + CHECKER_THICKNESS, 0, 0);
                }

                for (int i = 0; i < moderator.Board.Remaining[(int)Checker.Black]; i++)
                {
                    drawChecker(gl, Checker.Black, -FRAME_BAR_OFFSET_X - CHECKER_PILE_OFFSET, i + CHECKER_THICKNESS, 0, 0);
                }
            }

            #endregion

              
            //Texturing bothersomeness
            gl.Disable(OpenGL.GL_LIGHTING);
            gl.Enable(OpenGL.GL_TEXTURE_2D);
            gl.Color(1f, 1, 1);
            gl.Begin(OpenGL.GL_TRIANGLES);
            gl.Normal(0, 1, 0);
            gl.TexCoord(1, 1);
            gl.Vertex(120, 0, 50);
            gl.TexCoord(0, 1);
            gl.Vertex(0, 0, -150);
            gl.TexCoord(0, 0);
            gl.Vertex(-120, 0, 50);
            gl.End();
            gl.Disable(OpenGL.GL_TEXTURE_2D);
            gl.Enable(OpenGL.GL_LIGHTING);

            setPlasticColor(gl, Plastic.Wall);
            gl.Begin(OpenGL.GL_QUADS);
            gl.Normal(getNormal(new float[] { 0, 0, -150 }, new float[] { 0, 50, -150 }, new float[] { 120, 50, 50 }));
            gl.Vertex(0,0,-150);
            gl.Vertex(0,175,-150);
            gl.Vertex(-120,100,50);
            gl.Vertex(-120,0,50);

            gl.Normal(getNormal(new float[] { 120, 0, 50 }, new float[] { 120, 50, 50 }, new float[] { 0, 50, -150 }));
            gl.Vertex(120, 0, 50);
            gl.Vertex(120, 100, 50); 
            gl.Vertex(0, 175, -150);
            gl.Vertex(0, 0, -150);
            gl.End();


            gl.Flush();


        }

        private void drawChecker(OpenGL gl, Checker checker, double x, double y, double z, double angle)
        {
            double innerRadius = 4, outerRadius = 5, innerThickness = 0.2, outerThickness = 0.5;

            //set plastic color
            //if invalid checker, exit
            switch (checker & Checker.Color)
            {
                case Checker.Red:
                    setPlasticColor(gl, Plastic.Red);
                    break;
                case Checker.Black:
                    setPlasticColor(gl, Plastic.Black);
                    break;
                case Checker.Empty:
                    return;
            }

            if (!animation && !animation2 && (((int) (8*this.angle)) % 2) == 1 && (checker & Checker.Winner) == Checker.Winner)
            {
                setPlasticColor(gl, Plastic.Blue);
            }

            //gl.Rotate(0, (float)angle, 0);

            //top faces
            //depressed region
            gl.Begin(OpenGL.GL_TRIANGLE_FAN);
            gl.Normal(0, 1, 0);
            gl.Vertex(x + 0, y + innerThickness, z + 0);
            for (int i = CIRCLE_RESOLUTION; i >= 0; i--)
            {
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * innerRadius, y + innerThickness, z + circleY[i % CIRCLE_RESOLUTION] * innerRadius);
            }
            gl.End();

            //upper ridge
            gl.Begin(OpenGL.GL_QUADS);
            for (int i = CIRCLE_RESOLUTION; i >= 0; i--)
            {
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * innerRadius, y + outerThickness, z + circleY[i % CIRCLE_RESOLUTION] * innerRadius);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * innerRadius, y + outerThickness, z + circleY[(i + 1) % CIRCLE_RESOLUTION] * innerRadius);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * outerRadius, y + outerThickness, z + circleY[(i + 1) % CIRCLE_RESOLUTION] * outerRadius);
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * outerRadius, y + outerThickness, z + circleY[i % CIRCLE_RESOLUTION] * outerRadius);
            }
            gl.End();

            //bottom faces
            //depressed region
            gl.Begin(OpenGL.GL_TRIANGLE_FAN);
            gl.Normal(0, -1, 0);
            gl.Vertex(x + 0, y + -innerThickness, z + 0);
            for (int i = 0; i <= CIRCLE_RESOLUTION; i++)
            {
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * innerRadius, y + -innerThickness, z + circleY[i % CIRCLE_RESOLUTION] * innerRadius);
            }
            gl.End();

            //upper ridge
            gl.Begin(OpenGL.GL_QUADS);
            for (int i = CIRCLE_RESOLUTION; i >= 0; i--)
            {
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * outerRadius, y + -outerThickness, z + circleY[i % CIRCLE_RESOLUTION] * outerRadius);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * outerRadius, y + -outerThickness, z + circleY[(i + 1) % CIRCLE_RESOLUTION] * outerRadius);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * innerRadius, y + -outerThickness, z + circleY[(i + 1) % CIRCLE_RESOLUTION] * innerRadius);
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * innerRadius, y + -outerThickness, z + circleY[i % CIRCLE_RESOLUTION] * innerRadius);
            }
            gl.End();

            //outer edge
            gl.Begin(OpenGL.GL_QUADS);
            for (int i = 0; i <= CIRCLE_RESOLUTION; i++)
            {
                if (checkerOuterNormals[i % CIRCLE_RESOLUTION] == null)
                {
                    checkerOuterNormals[i % CIRCLE_RESOLUTION] = getNormal(new float[] { (float)(x + circleX[i % CIRCLE_RESOLUTION] * outerRadius), (float)(y + outerThickness), (float)(z + circleY[i % CIRCLE_RESOLUTION] * outerRadius) },
                        new float[] { (float)(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * outerRadius), (float)(y + -outerThickness), (float)(z + circleY[(i + 1) % CIRCLE_RESOLUTION] * outerRadius) },
                        new float[] { (float)(x + circleX[i % CIRCLE_RESOLUTION] * outerRadius), (float)(y + -outerThickness), (float)(z + circleY[i % CIRCLE_RESOLUTION] * outerRadius) });
                }
                gl.Normal(checkerOuterNormals[i % CIRCLE_RESOLUTION]);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * outerRadius, y + outerThickness, z + circleY[(i + 1) % CIRCLE_RESOLUTION] * outerRadius);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * outerRadius, y + -outerThickness, z + circleY[(i + 1) % CIRCLE_RESOLUTION] * outerRadius);
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * outerRadius, y + -outerThickness, z + circleY[i % CIRCLE_RESOLUTION] * outerRadius);
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * outerRadius, y + outerThickness, z + circleY[i % CIRCLE_RESOLUTION] * outerRadius);
            }
            gl.End();

            //side between raised edge and depressed region
            gl.Begin(OpenGL.GL_QUADS);
            for (int i = 0; i <= CIRCLE_RESOLUTION; i++)
            {
                if (checkerInnerNormals[i % CIRCLE_RESOLUTION] == null)
                {
                    checkerInnerNormals[i % CIRCLE_RESOLUTION] = new float[3];
                    checkerInnerNormals[i % CIRCLE_RESOLUTION][0] = -checkerOuterNormals[i % CIRCLE_RESOLUTION][0];
                    checkerInnerNormals[i % CIRCLE_RESOLUTION][1] = -checkerOuterNormals[i % CIRCLE_RESOLUTION][1];
                    checkerInnerNormals[i % CIRCLE_RESOLUTION][2] = -checkerOuterNormals[i % CIRCLE_RESOLUTION][2];
                }
                gl.Normal(checkerInnerNormals[i % CIRCLE_RESOLUTION]);
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * innerRadius, y + outerThickness, z + circleY[i % CIRCLE_RESOLUTION] * innerRadius);
                gl.Vertex(x + circleX[i % CIRCLE_RESOLUTION] * innerRadius, y + -outerThickness, z + circleY[i % CIRCLE_RESOLUTION] * innerRadius);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * innerRadius, y + -outerThickness, z + circleY[(i + 1) % CIRCLE_RESOLUTION] * innerRadius);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * innerRadius, y + outerThickness, z + circleY[(i + 1) % CIRCLE_RESOLUTION] * innerRadius);
            }
            gl.End();

            //gl.Rotate(0, (float)-angle, 0);
        }

        private void drawFrameModule(OpenGL gl, double x, double y)
        {
            double innerCircleRadius = 4, outerCircleRadius = 4.5, zOffset = CHECKER_THICKNESS / 2;

            //draws north/east/west/south triangles on front and back faces
            gl.Begin(OpenGL.GL_TRIANGLES);

            //front face
            gl.Normal(0.0, 0.0, 1.0);
            gl.Vertex(x + -HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            gl.Vertex(x + -HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            gl.Vertex(x + circleX[2 * CIRCLE_RESOLUTION / 4] * outerCircleRadius, y + circleY[2 * CIRCLE_RESOLUTION / 4] * outerCircleRadius, zOffset + FRAME_THICKNESS);

            gl.Vertex(x + HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            gl.Vertex(x + -HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            gl.Vertex(x + circleX[CIRCLE_RESOLUTION / 4] * outerCircleRadius, y + circleY[CIRCLE_RESOLUTION / 4] * outerCircleRadius, zOffset + FRAME_THICKNESS);

            gl.Vertex(x + HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            gl.Vertex(x + HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            gl.Vertex(x + circleX[0] * outerCircleRadius, y + circleY[0] * outerCircleRadius, zOffset + FRAME_THICKNESS);

            gl.Vertex(x + -HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            gl.Vertex(x + HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            gl.Vertex(x + circleX[3 * CIRCLE_RESOLUTION / 4] * outerCircleRadius, y + circleY[3 * CIRCLE_RESOLUTION / 4] * outerCircleRadius, zOffset + FRAME_THICKNESS);

            //back face
            gl.Normal(0.0, 0.0, -1.0);
            gl.Vertex(x + -HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset);
            gl.Vertex(x + -HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset);
            gl.Vertex(x + circleX[2 * CIRCLE_RESOLUTION / 4] * innerCircleRadius, y + circleY[2 * CIRCLE_RESOLUTION / 4] * innerCircleRadius, zOffset);

            gl.Vertex(x + -HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset);
            gl.Vertex(x + HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset);
            gl.Vertex(x + circleX[CIRCLE_RESOLUTION / 4] * innerCircleRadius, y + circleY[CIRCLE_RESOLUTION / 4] * innerCircleRadius, zOffset);

            gl.Vertex(x + HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset);
            gl.Vertex(x + HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset);
            gl.Vertex(x + circleX[0] * innerCircleRadius, y + circleY[0] * innerCircleRadius, zOffset);

            gl.Vertex(x + HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset);
            gl.Vertex(x + -HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset);
            gl.Vertex(x + circleX[3 * CIRCLE_RESOLUTION / 4] * innerCircleRadius, y + circleY[3 * CIRCLE_RESOLUTION / 4] * innerCircleRadius, zOffset);

            gl.End();

            //draws fans to corners for front face
            gl.Normal(0.0, 0.0, 1.0);
            gl.Begin(OpenGL.GL_TRIANGLE_FAN);

            gl.Vertex(x + HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            for (int i = CIRCLE_RESOLUTION / 4; i >= 0; i--)
            {
                gl.Vertex(x + circleX[i] * outerCircleRadius, y + circleY[i] * outerCircleRadius, zOffset + FRAME_THICKNESS);
            }

            gl.End();

            gl.Begin(OpenGL.GL_TRIANGLE_FAN);

            gl.Vertex(x + -HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            for (int i = 2 * CIRCLE_RESOLUTION / 4; i >= CIRCLE_RESOLUTION / 4; i--)
            {
                gl.Vertex(x + circleX[i] * outerCircleRadius, y + circleY[i] * outerCircleRadius, zOffset + FRAME_THICKNESS);
            }

            gl.End();

            gl.Begin(OpenGL.GL_TRIANGLE_FAN);

            gl.Vertex(x + -HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            for (int i = 3 * CIRCLE_RESOLUTION / 4; i >= 2 * CIRCLE_RESOLUTION / 4; i--)
            {
                gl.Vertex(x + circleX[i] * outerCircleRadius, y + circleY[i] * outerCircleRadius, zOffset + FRAME_THICKNESS);
            }

            gl.End();

            gl.Begin(OpenGL.GL_TRIANGLE_FAN);

            gl.Vertex(x + HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset + FRAME_THICKNESS);
            gl.Vertex(x + circleX[0] * outerCircleRadius, y + circleY[0] * outerCircleRadius, zOffset + FRAME_THICKNESS);
            for (int i = CIRCLE_RESOLUTION - 1; i >= 3 * CIRCLE_RESOLUTION / 4; i--)
            {
                gl.Vertex(x + circleX[i] * outerCircleRadius, y + circleY[i] * outerCircleRadius, zOffset + FRAME_THICKNESS);
            }

            gl.End();

            //draws fans to corners for back face
            gl.Normal(0.0, 0.0, -1.0);
            gl.Begin(OpenGL.GL_TRIANGLE_FAN);

            gl.Vertex(x + HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset);
            for (int i = 0; i <= CIRCLE_RESOLUTION / 4; i++)
            {
                gl.Vertex(x + circleX[i] * innerCircleRadius, y + circleY[i] * innerCircleRadius, zOffset);
            }

            gl.End();
            gl.Begin(OpenGL.GL_TRIANGLE_FAN);

            gl.Vertex(x + -HALF_MODULE_SIZE, y + HALF_MODULE_SIZE, zOffset);
            for (int i = CIRCLE_RESOLUTION / 4; i <= 2 * CIRCLE_RESOLUTION / 4; i++)
            {
                gl.Vertex(x + circleX[i] * innerCircleRadius, y + circleY[i] * innerCircleRadius, zOffset);
            }

            gl.End();
            gl.Begin(OpenGL.GL_TRIANGLE_FAN);

            gl.Vertex(x + -HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset);
            for (int i = 2 * CIRCLE_RESOLUTION / 4; i <= 3 * CIRCLE_RESOLUTION / 4; i++)
            {
                gl.Vertex(x + circleX[i] * innerCircleRadius, y + circleY[i] * innerCircleRadius, zOffset);
            }

            gl.End();
            gl.Begin(OpenGL.GL_TRIANGLE_FAN);

            gl.Vertex(x + HALF_MODULE_SIZE, y + -HALF_MODULE_SIZE, zOffset);
            for (int i = 3 * CIRCLE_RESOLUTION / 4; i <= CIRCLE_RESOLUTION - 1; i++)
            {
                gl.Vertex(x + circleX[i] * innerCircleRadius, y + circleY[i] * innerCircleRadius, zOffset);
            }
            gl.Vertex(x + circleX[0] * innerCircleRadius, y + circleY[0] * innerCircleRadius, zOffset);

            gl.End();

            //draws the circular opening faces
            gl.Begin(OpenGL.GL_QUADS);

            for (int i = 0; i < CIRCLE_RESOLUTION; i++)
            {
                //lazy load normals (to simplify calculations)
                if (frameNormals[i] == null)
                {
                    frameNormals[i] = getNormal(new float[] { (float)(circleX[i] * innerCircleRadius), (float)(circleY[i] * innerCircleRadius), (float)zOffset },
                        new float[] { (float)(circleX[i] * outerCircleRadius), (float)(circleY[i] * outerCircleRadius), (float)(zOffset + FRAME_THICKNESS) },
                        new float[] { (float)(circleX[(i + 1) % CIRCLE_RESOLUTION] * outerCircleRadius), (float)(circleY[(i + 1) % CIRCLE_RESOLUTION] * outerCircleRadius), (float)(zOffset + FRAME_THICKNESS) });
                }

                gl.Normal(frameNormals[i]);
                gl.Vertex(x + circleX[i] * innerCircleRadius, y + circleY[i] * innerCircleRadius, zOffset);
                gl.Vertex(x + circleX[i] * outerCircleRadius, y + circleY[i] * outerCircleRadius, zOffset + FRAME_THICKNESS);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * outerCircleRadius, y + circleY[(i + 1) % CIRCLE_RESOLUTION] * outerCircleRadius, zOffset + FRAME_THICKNESS);
                gl.Vertex(x + circleX[(i + 1) % CIRCLE_RESOLUTION] * innerCircleRadius, y + circleY[(i + 1) % CIRCLE_RESOLUTION] * innerCircleRadius, zOffset);
            }
            gl.End();
        }

        private void setPlasticColor(OpenGL gl, Plastic plastic)
        {
            if (currentPlastic == plastic)
            {
                return;
            }
            currentPlastic = plastic;
            switch (plastic)
            {
                case Plastic.Yellow:
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_AMBIENT, new float[] { 0.5f, 0.5f, 0.0f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_DIFFUSE, new float[] { 0.7f, 0.6f, 0.0f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SPECULAR, new float[] { 0.8f, 0.7f, 0.2f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SHININESS, 20.0f);
                    break;
                case Plastic.Black:
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_AMBIENT, new float[] { 0.1f, 0.1f, 0.1f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_DIFFUSE, new float[] { 0.1f, 0.1f, 0.1f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SPECULAR, new float[] { 0.5f, 0.5f, 0.5f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SHININESS, 20.0f);
                    break;
                case Plastic.Red:
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_AMBIENT, new float[] { 0.5f, 0.0f, 0.0f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_DIFFUSE, new float[] { 0.7f, 0.0f, 0.0f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SPECULAR, new float[] { 0.8f, 0.2f, 0.2f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SHININESS, 20.0f);
                    break;
                case Plastic.Blue:
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_AMBIENT, new float[] { 0.0f, 0.0f, 0.5f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_DIFFUSE, new float[] { 0.0f, 0.0f, 0.7f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SPECULAR, new float[] { 0.2f, 0.2f, 0.8f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SHININESS, 20.0f);
                    break;
                case Plastic.Wall:
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_AMBIENT, new float[] {.416f, .855f,.910f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_DIFFUSE, new float[] { .416f, .855f, .910f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SPECULAR, new float[] { .416f, .855f, .910f, 1.0f });
                    gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SHININESS, 20.0f);
                    break;
            }
        }

        private float[] getNormal(float[] point1, float[] point2, float[] point3)
        {
            float[] ans = new float[3], a = new float[3], b = new float[3];
            a[0] = point2[0] - point1[0];
            a[1] = point2[1] - point1[1];
            a[2] = point2[2] - point1[2];
            b[0] = point3[0] - point1[0];
            b[1] = point3[1] - point1[1];
            b[2] = point3[2] - point1[2];
            ans[0] = a[1] * b[2] - b[1] * a[2];
            ans[1] = a[2] * b[0] - b[2] * a[0];
            ans[2] = a[0] * b[1] - b[0] * a[1];
            return ans;
        }

        private void Window_KeyDown(object sender, KeyEventArgs e)
        {
            /*switch (e.Key)
            {
                case Key.D1:
                    makeMove(0);
                    break;
                case Key.D2:
                    makeMove(1);
                    break;
                case Key.D3:
                    makeMove(2);
                    break;
                case Key.D4:
                    makeMove(3);
                    break;
                case Key.D5:
                    makeMove(4);
                    break;
                case Key.D6:
                    makeMove(5);
                    break;
                case Key.D7:
                    makeMove(6);
                    break;
                case Key.Space:
                    for (int i = 0; i < ROWS; i++)
                    {
                        for (int j = 0; j < COLUMNS; j++)
                        {
                            board[i, j] = Checker.Empty;
                        }
                    }
                    remainingBlack = remainingRed = 21;
                    nextChecker = Checker.Red;
                    break;
                default:
                    return;
            }*/
        }

        private void buttonGo_Click(object sender, RoutedEventArgs e)
        {
            ((Button)sender).IsEnabled = false;
            labelWinner.Content = "";
            comboBoxBlack.IsEnabled = false;
            comboBoxRed.IsEnabled = false;
            if (boardFull)
            {
                boardClearing = true;
                boardClearer.RunWorkerAsync();
                tempBoard = moderator.Board;
            }
            moderator = new Moderator((String)comboBoxRed.SelectedItem, (String)comboBoxBlack.SelectedItem);
            gameWorker.RunWorkerAsync();
        }

        void boardClearer_DoWork(object sender, DoWorkEventArgs e)
        {
            animation2 = true;
            movingVelY = 0;
            movingBoardY = 0;
            movingTargetY = -70;

            while (animation2)
            {
                Thread.Sleep(50);
            }
            boardClearing = false;
        }

        void gameWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            int row, column = -1;
            Checker checker;

            while (boardClearing)
                Thread.Sleep(50);

            while (!moderator.NextMove(column, out row, out column, out checker))
            {
                movingRow = row;
                movingCol = column;
                movingCheckerX = column * MODULE_SIZE;
                movingCheckerY = 60;
                movingTargetY = row * MODULE_SIZE;
                movingVelY = 0;
                animation = true;
                movingChecker = checker;
                movingBounceCount = 0;

                while (animation)
                {
                    Thread.Sleep(50);
                }

                Thread.Sleep(1000);
                /*for (int i = 0; i < 3600; i++)
                {
                    angle += 0.001;
                    Thread.Sleep(3);
                }*/
            }


            movingRow = row;
            movingCol = column;
            movingCheckerX = column * MODULE_SIZE;
            movingCheckerY = 60;
            movingTargetY = row * MODULE_SIZE;
            movingVelY = 0;
            animation = true;
            movingChecker = checker;
            movingBounceCount = 0;

            while (animation)
            {
                Thread.Sleep(50);
            }
            winner = checker;
        }

        private void gameWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            moderator.GameOver();
            buttonGo.IsEnabled = true;
            comboBoxRed.IsEnabled = true;
            comboBoxBlack.IsEnabled = true;
            boardFull = true;
            labelWinner.Content = (winner == Checker.Red ? (String)comboBoxRed.SelectedItem : (String)comboBoxBlack.SelectedItem) + " won!";
            
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void makeMove(int column)
        {
            /*for (int i = 0; i < ROWS; i++)
            {
                if (board[i, column] == Checker.Empty)
                {
                    board[i, column] = nextChecker;//|Checker.Moving;
                    if (nextChecker == Checker.Red) { remainingRed--; }
                    else { remainingBlack--; }
                    break;
                }
            }
            nextChecker = nextChecker == Checker.Red ? Checker.Black : Checker.Red;*/
        }

        enum Plastic
        {
            Yellow, Blue, Black, Red, Wall
        }
    }


}
